﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Atlepage.State;

namespace Atlepage.Lexical
{
    public class LexerFactory<TToken> where TToken : Token<TToken>, new()
    {
        private TokenInfo[] tokenInfos;
        private Regex master;
        private IEnum kind;
        private Lexer<TToken> lexer;

        public static Lexer<TToken> CreateLexer(Type handlerType, IEnum kind, object actor)
        {
            LexerFactory<TToken> factory = new LexerFactory<TToken>();
            if (!factory.LoadFromDisk(kind))
            {
                if (factory.Generate(handlerType, kind))
                    factory.SaveToDisk();
                else
                    throw new InvalidOperationException();
            }
            return factory.CreateLexer(actor);
        }

        public bool LoadFromDisk(IEnum kind)
        {
            Manager<SerializableLexer> manager = new Manager<SerializableLexer>(".tll");
            SerializableLexer slexer = manager.Load();
            if (slexer != null)
                lexer = new Lexer<TToken>(slexer, kind);
            return lexer != null;
        }

        public void SaveToDisk()
        {
            Manager<SerializableLexer> manager = new Manager<SerializableLexer>(".tll");
            manager.Save(new SerializableLexer(lexer.tokenInfos, lexer.master));
        }

        public bool Generate(Type handlerType, IEnum kind)
        {
            this.kind = kind;
            BuildTokenInfo(handlerType);
            BuildMaster();
            lexer = new Lexer<TToken>(tokenInfos, master, kind);
            return true;
        }

        public Lexer<TToken> CreateLexer(object actor)
        {
            Lexer<TToken> l = lexer.Clone();
            l.Initialize(actor);
            return l;
        }

        private void BuildMaster()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(@"\G(");
            for (int i = 0; i < tokenInfos.Length; i++)
                sb.AppendFormat(@"{2}(?<{0}>{1})", tokenInfos[i].GroupName, tokenInfos[i].Expression, i == 0 ? "" : "|");
            sb.Append(")");
            master = new Regex(sb.ToString());
        }

        private void BuildTokenInfo(Type handlerType)
        {
            var tokens = from a in (TokenAttribute[])handlerType.GetCustomAttributes(typeof(TokenAttribute), true)
                         select new { type = a.Name, re = a.Expression };

            var methods = from m in handlerType.GetMethods()
                          // check return type
                          where m.ReturnType == typeof(TToken)
                          // check parameter types
                          let ps = m.GetParameters()
                          where ps.Length == 2 && ps[0].ParameterType == typeof(Group) && ps[1].ParameterType == typeof(TToken)
                          // get attributes and join them with methods
                          let attribs = (TokenAttribute[])m.GetCustomAttributes(typeof(TokenAttribute), true)
                          where attribs.Length > 0
                          from a in attribs
                          where a.Name != null || m.Name.StartsWith("t_")
                          select new
                          {
                              type = a.Name != null ? a.Name : m.Name.Substring(2),
                              re = a.Expression,
                              handlerName = m.Name,
                              handler = m
                          };

            int i = 0;
            List<TokenInfo> infos = new List<TokenInfo>();
            foreach (var method in methods)
            {
                TokenInfo info = new TokenInfo();
                info.Type = kind[method.type];
                info.GroupName = "g" + i++;
                info.Handler = method.handler;
                info.HandlerName = method.handlerName;
                info.Expression = method.re;
                infos.Add(info);
            }

            foreach (var token in tokens)
            {
                TokenInfo info = new TokenInfo();
                if (token.type == null)
                    info.Type = -1;
                else
                    info.Type = kind[token.type];
                info.GroupName = "g" + i++;
                info.Expression = token.re;
                infos.Add(info);
            }

            tokenInfos = infos.ToArray();
        }

    }
}
